using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows.Controls;
using WindowsPhoneEssentials.Diagnostics;
using WindowsPhoneMVC.Interfaces;
using WindowsPhoneMVC.Navigation.Exceptions;

namespace WindowsPhoneMVC.Navigation
{
    public class DefaultViewLocator : IViewLocator
    {
        private volatile Dictionary<string, Type> views;
        private readonly List<Assembly> assembliesToScan;
        private readonly object viewLookupLock = new object();

        public DefaultViewLocator(Assembly assemblyToScan, params Assembly[] additionalAssemblies)
        {
            assembliesToScan = new List<Assembly>(additionalAssemblies) { assemblyToScan };
        }

        public Dictionary<string, Type> Views
        {
            get
            {
                if (views == null)
                    lock (viewLookupLock)
                        if (views == null)
                            using (TraceScope.Create(this, TraceLevel.Debug, () => "Scanning assemblies for views and building lookup"))
                            {
                                var frameworkElement = typeof (UserControl);

                                views = assembliesToScan
                                    .SelectMany(a => a.GetTypes())
                                    .Where(t => t.IsClass && NameMatchesConvention(t) && frameworkElement.IsAssignableFrom(t))
                                    .SelectMany(GetPermuations)
                                    .ToDictionary(k => GetKey(k.Namespace.Split('.').Last(), k.Name), k => k.Type);
                            }
                return views;
            }
        }

        public void AddAssemblyToScan(Assembly assembly)
        {
            assembliesToScan.Add(assembly);
            lock (viewLookupLock)
                views = null;
        }

        private static IEnumerable<Registration> GetPermuations(Type t)
        {
            var shortName = RemoveSuffixes(t.Name);
            if (shortName == t.Name)
            {
                return new[]
                           {
                               new Registration {Name = t.Name, Namespace = t.Namespace, Type = t}
                           };
            }
            return new[]
                       {
                           new Registration {Name = shortName, Namespace = t.Namespace, Type = t},
                           new Registration {Name = t.Name, Namespace = t.Namespace, Type = t}
                       };
        }

        private static bool NameMatchesConvention(MemberInfo t)
        {
            return t.Name.StartsWith("View") || t.Name.StartsWith("Page") ||
                   t.Name.EndsWith("View") || t.Name.EndsWith("Page") ||
                   t.Name.EndsWith("Dialog");
        }

        public static string RemoveSuffixes(string name)
        {
            if (name.EndsWith("View"))
                return name.Substring(0, name.Length - 4);
            if (name.EndsWith("Page"))
                return name.Substring(0, name.Length - 4);
            if (name.EndsWith("Dialog"))
                return name.Substring(0, name.Length - 6);
            if (name.EndsWith("Dialogue"))
                return name.Substring(0, name.Length - 8);

            return name;
        }

        public UserControl Locate(string controllerName, string viewName)
        {
            var key = GetKey(controllerName, viewName);
            if (!Views.ContainsKey(key))
                throw new ViewNotFoundException(viewName);

            return (UserControl)Activator.CreateInstance(Views[key]);
        }

        private static string GetKey(string controllerName, string viewName)
        {
            return string.Format("{0}.{1}", controllerName.Replace("Controller", string.Empty), viewName);
        }

        private class Registration
        {
            public string Name { get; set; }
            public string Namespace { get; set; }
            public Type Type { get; set; }
        }
    }
}